﻿using System;
using System.Drawing;
using System.Windows.Forms;
using SpriteAnimator.Classes;
using System.Collections.Generic;

namespace SpriteAnimator
{
    /// <summary>
    /// Fenêtre de définition des animations
    /// </summary>
    public partial class frmAnimation : Form
    {
        #region Variables d'instances

        private int m_iCount;
        private bool m_bSave;
        private bool m_bRefresh;
        private string m_sCurrentEdit;
        private Sprite m_oSprite;

        #endregion

        #region Constructeur

        /// <summary>
        /// Constructeur de la form
        /// </summary>
        /// <param name="p_oSprite">Définition du sprite</param>
        public frmAnimation(Sprite p_oSprite)
        {
            this.InitializeComponent();

            // *** Initialisation ***

            this.m_iCount = 0;
            this.m_bRefresh = false;
            this.m_bSave = false;
            this.m_sCurrentEdit = string.Empty;
            this.m_oSprite = p_oSprite;
            this.picPreview.BackColor = p_oSprite.BackColor;
            this.chkCadre.Checked = Utils.GetRegistryValue("SpriteRectangle", "1", Utils.REGISTRY_KEY).Equals("1");

            // *** Chargement des infos pour le story board ***

            this.picAnimation.BackColor = p_oSprite.BackColor;
            this.picAnimation.Texture = p_oSprite.Texture;

            foreach ( Rectangle oFrame in p_oSprite.Frames )
                this.picAnimation.Frames.Add(oFrame);

            // *** Info texture ***

            this.lblInformation.Text = String.Format("Information texture : {0} x {1}", this.m_oSprite.Texture.Width, this.m_oSprite.Texture.Height);

            // *** Info animation ***

            this.LoadAnimation(null);

            // *** Binding des données sur l'interface ***

            this.GenerateFrames();
        }

        #endregion

        #region Evénement de l'IHM

        /// <summary>
        /// Se produit sur la fermeture de la fenêtre
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void frmAnimation_FormClosing(object sender, FormClosingEventArgs e)
        {
            if ( !this.CheckModify() )
                e.Cancel = true;
            else if ( this.m_bSave )
            {
                DialogResult oTmp = MessageBox.Show(this, "Des modifications ont été apporté, voulez-vous les enregistrer ?", "Animation", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information);

                if ( oTmp == DialogResult.Cancel )
                    e.Cancel = true;
                else if ( oTmp == DialogResult.Yes )
                    this.DialogResult = DialogResult.OK;
            }
        }

        /// <summary>
        /// Active ou non le cadre de rendu
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void chkCadre_CheckedChanged(object sender, EventArgs e)
        {
            this.PreviewFrame();
        }

        /// <summary>
        /// Au changement de frame
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void lstFrames_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.PreviewFrame();
        }

        /// <summary>
        /// Ajoute la frame si double click
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void lstFrames_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.cmdAdd_Click(this.cmdAdd, EventArgs.Empty);
        }

        /// <summary>
        /// Au changement d'index animation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void lstAnim_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.txtInfoAnim.Clear();
            this.cmdModifier.Enabled = false;
            this.cmdSupprimer.Enabled = false;

            if ( this.lstAnim.SelectedItem != null )
            {
                this.cmdModifier.Enabled = true;
                this.cmdSupprimer.Enabled = true;
                this.txtInfoAnim.AppendText((this.lstAnim.SelectedItem as SpriteSequence).GenerateRapport());
            }
        }

        /// <summary>
        /// Sur l'annulation des travaux
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdFermer_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
            this.Close();
        }

        /// <summary>
        /// Sur la validation des travaux
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdValider_Click(object sender, EventArgs e)
        {
            this.m_bSave = false;
            this.DialogResult = DialogResult.OK;
            this.Close();
        }

        /// <summary>
        /// Ajoute une frame
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdAdd_Click(object sender, EventArgs e)
        {
            if ( this.lstFrames.SelectedIndices.Count > 0 )
                this.picAnimation.Add(this.lstFrames.SelectedIndices[0]);
        }

        /// <summary>
        /// Au changement d'une animation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdModifier_Click(object sender, EventArgs e)
        {
            if ( this.lstAnim.SelectedItem != null )
            {
                this.txtInfoAnim.Clear();
                this.txtInfoAnim.AppendText((this.lstAnim.SelectedItem as SpriteSequence).GenerateRapport());
                this.LoadStoryBoard((SpriteSequence)this.lstAnim.SelectedItem);
            }
        }

        /// <summary>
        /// A la création d'une animation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdCreer_Click(object sender, EventArgs e)
        {
            // *** Incrément pour le nom générique ***

            this.m_iCount++;

            // *** Affichage de la demande de création ***

            using ( frmInputBox oInput = new frmInputBox("Créer une animation", "Saisir le nom de l'animation", "Anim" + this.m_iCount.ToString("00")) )
            {
                // *** Demande de saisie de nom ***

                if ( oInput.ShowDialog(this) == DialogResult.Cancel )
                    return;

                // *** Test du nom ***

                if ( this.ExistName(oInput.Value) )
                {
                    MessageBox.Show(this, "Ce nom d'animation existe déjà.", "Animation", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                // *** Création de la sequence ***

                SpriteSequence oSeq = new SpriteSequence(oInput.Value);
                oSeq.Type = SpriteAnimationType.List;

                this.m_oSprite.Sequences.Add(oSeq);

                // *** On rafraichis ***

                this.LoadAnimation(oInput.Value);

                // *** On lance la modification ***

                this.cmdModifier_Click(this.cmdModifier, EventArgs.Empty);

                // *** Mode modifié ***

                this.SetModify(true);
            }
        }

        /// <summary>
        /// Supprime une animation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdSupprimer_Click(object sender, EventArgs e)
        {
            // *** Si pas de sélection on ne fait rien ***

            if ( this.lstAnim.SelectedItem == null )
                return;

            // *** Demande de suppression ***

            if ( MessageBox.Show(this, string.Format("Voulez-vous vraiment supprimer l'animation {0} ?", (this.lstAnim.SelectedItem as SpriteSequence).Name), "Animation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No )
                return;

            // *** Suppression ***

            if ( (this.lstAnim.SelectedItem as SpriteSequence).Name.Equals(this.m_sCurrentEdit) )
                this.ClearAnimation();

            this.m_oSprite.Sequences.Remove(this.lstAnim.SelectedItem as SpriteSequence);
            this.lstAnim.Items.Remove(this.lstAnim.SelectedItem);

            // *** Mode modifié ***

            this.SetModify(true);
        }

        /// <summary>
        /// Sauvegarde le travail en cours
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdSave_Click(object sender, EventArgs e)
        {
            string sIndex = null;
            string sMessage = string.Empty;

            // *** Si pas de modif en cours on sort ***

            if ( string.IsNullOrEmpty(this.m_sCurrentEdit) )
                return;

            // *** Vérification du nom ***

            if ( string.IsNullOrEmpty(this.txtIdent.Text) )
            {
                MessageBox.Show(this, "Vous devez saisir un nom d'animation.", "Animation", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            if ( !this.txtIdent.Text.ToLower().Equals(this.m_sCurrentEdit.ToLower()) && this.ExistName(this.txtIdent.Text) )
            {
                MessageBox.Show(this, "Ce nom d'animation existe déjà.", "Animation", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            // *** Vérification mode range ***

            if ( !this.picAnimation.Verify(ref sMessage) )
            {
                MessageBox.Show(this, sMessage, "Animation", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            // *** On lance la sauvegarde ***

            this.SaveAnimation();

            // *** On rafraichis ***

            if ( this.lstAnim.SelectedItem != null )
                sIndex = (this.lstAnim.SelectedItem as SpriteSequence).Name;

            this.LoadAnimation(sIndex);
            this.ClearAnimation();
        }

        /// <summary>
        /// Va sur la frame précédente
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdPrevious_Click(object sender, EventArgs e)
        {
            this.picAnimation.FramePrevious();
        }

        /// <summary>
        /// Va sur la frame suivante
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdNext_Click(object sender, EventArgs e)
        {
            this.picAnimation.FrameNext();
        }

        /// <summary>
        /// Supprime une frame animation sélectionnée
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdRemove_Click(object sender, EventArgs e)
        {
            // *** Vérification ***

            if ( this.picAnimation.SelectedIndex < 0 )
                return;

            // *** On retire la frame ***

            this.picAnimation.RemoveAt(this.picAnimation.SelectedIndex);
        }

        /// <summary>
        /// Changement de sélection dans le story board
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void picAnimation_SelectedIndexChange(object sender, EventArgs e)
        {
            this.cmdRemove.Enabled = this.picAnimation.SelectedIndex > -1;
        }

        /// <summary>
        /// Gère l'état des boutons de page
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void picAnimation_ControlPageChange(object sender, SpriteAnimator.Controls.ControlPageArgs e)
        {
            this.cmdPrevious.Enabled = e.HavePageReturn;
            this.cmdNext.Enabled = e.HavePageFoward;
        }

        /// <summary>
        /// Se lance quand le story board est modifié
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void picAnimation_Modified(object sender, EventArgs e)
        {
            // *** Filtre d'événement

            if ( this.m_bRefresh )
                return;

            this.SetModify(true);
        }

        /// <summary>
        /// Sur le changement d'état du radio button de mode
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void rbListe_CheckedChanged(object sender, EventArgs e)
        {
            // *** Filtre d'événement

            if ( this.m_bRefresh )
                return;

            // *** Changement d'état du contrôle ***

            this.picAnimation.RangeMode = !this.rbListe.Checked;
        }

        /// <summary>
        /// Sur le changement de nom d'animation
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void txtIdent_TextChanged(object sender, EventArgs e)
        {
            // *** Filtre d'événement

            if ( this.m_bRefresh )
                return;

            this.SetModify(true);
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Défini le titre de la fenêtre
        /// </summary>
        private void BuildTitle()
        {
            this.Text = String.Format("Animation {0}", this.m_bSave ? "(Modifié)" : String.Empty);
        }

        /// <summary>
        /// Vérifie si des donnée sont en cours de modification
        /// </summary>
        /// <returns>TRUE rien a signaler, FALSE des données sont en validation</returns>
        private bool CheckModify()
        {
            if ( this.picAnimation.ToSave )
            {
                DialogResult oTmp = MessageBox.Show(this, "Le story board n'a pas été enregistré. Voulez-vous sauvegarder les modifications ?", "Animation", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information);

                if ( oTmp == DialogResult.Cancel )
                    return (false);
                else if ( oTmp == DialogResult.Yes )
                    this.cmdSave_Click(this.cmdSave, EventArgs.Empty);
            }

            return (true);
        }

        /// <summary>
        /// Vide la zone d'animation
        /// </summary>
        private void ClearAnimation()
        {
            try
            {
                this.m_bRefresh = true;
                this.picAnimation.Clear();

                this.rbListe.Checked = true;
                this.rbRange.Checked = true;

                this.m_sCurrentEdit = string.Empty;
                this.txtIdent.Text = string.Empty;

                this.cmdRemove.Enabled = false;
                this.gbxInformation.Enabled = false;

                this.picAnimation.ToSave = false;
            }
            finally
            {
                this.m_bRefresh = true;
            }
        }

        /// <summary>
        /// Vérifie si un nom de séquence existe déjà
        /// </summary>
        /// <param name="p_sName">Nom à tester</param>
        /// <returns>TRUE existe déjà, FALSE sinon</returns>
        private bool ExistName(string p_sName)
        {
            p_sName = p_sName.ToLower();

            foreach ( SpriteSequence oSeq in this.m_oSprite.Sequences )
            {
                if ( oSeq.Name.ToLower().Equals(p_sName) )
                    return (true);
            }

            return (false);
        }

        /// <summary>
        /// Génère les frames pour l'image list
        /// </summary>
        private void GenerateFrames()
        {
            // *** Initialisation ***

            this.lstFrames.Items.Clear();

            // *** Création des images ***

            foreach ( Rectangle oRect in this.m_oSprite.Frames )
            {
                // *** Ajout à la liste ***

                ListViewItem oItem = new ListViewItem();
                oItem.Tag = oRect;
                oItem.Text = string.Empty;
                oItem.SubItems.Add(oRect.X.ToString());
                oItem.SubItems.Add(oRect.Y.ToString());
                oItem.SubItems.Add(oRect.Width.ToString());
                oItem.SubItems.Add(oRect.Height.ToString());

                this.lstFrames.Items.Add(oItem);
            }

            // *** Numérotation ***

            this.Renumeroter();

            // *** On sélectionne le premier ***

            if ( this.lstFrames.Items.Count > 0 )
                this.lstFrames.Items[0].Selected = true;
        }

        /// <summary>
        /// Charge la liste des animations
        /// </summary>
        private void LoadAnimation(string p_sName)
        {
            int iIndex = 0;
            int iSelected = -1;

            // *** On vide la liste ***

            this.lstAnim.Items.Clear();

            // *** On charge les séquences ***

            foreach ( SpriteSequence oSeq in this.m_oSprite.Sequences )
            {
                this.lstAnim.Items.Add(oSeq);

                if ( oSeq.Name.Equals(p_sName) )
                    iSelected = iIndex;

                iIndex++;
            }

            // *** On sélectionne la première ***

            if ( this.lstAnim.Items.Count > 0 )
                this.lstAnim.SelectedIndex = iSelected;
        }

        /// <summary>
        /// Charge un story board
        /// </summary>
        /// <param name="p_oSequence">Définition de la séquence</param>
        private void LoadStoryBoard(SpriteSequence p_oSequence)
        {
            try
            {
                // *** Vérification ***

                if ( !this.CheckModify() )
                    return;

                // *** On désactive les events ***

                this.m_bRefresh = true;

                // *** On vide l'ancien ***

                this.picAnimation.ReDraw = false;
                this.picAnimation.Clear();

                // *** On ajoute les références frame ***

                this.m_sCurrentEdit = p_oSequence.Name;
                this.txtIdent.Text = p_oSequence.Name;
                this.picAnimation.RangeMode = p_oSequence.Type == SpriteAnimationType.Range;
                this.rbRange.Checked = this.picAnimation.RangeMode;
                this.rbListe.Checked = !this.picAnimation.RangeMode;

                // *** Ajout des frames ***

                foreach ( int iFrame in p_oSequence.FramesIndexes )
                    this.picAnimation.Add(iFrame);

                // *** On se positionne sur la première ***

                this.picAnimation.GoFirst();

                // *** On active la zone de saisie animation ***

                this.gbxInformation.Enabled = true;

                // *** Mise à jour indicateur de modification ***

                this.SetModify(false);
            }
            finally
            {
                this.m_bRefresh = false;
                this.picAnimation.ReDraw = true;
            }
        }

        /// <summary>
        /// Lance la prévisualisation de la frame
        /// </summary>
        private void PreviewFrame()
        {
            // *** Libération de l'ancienne image ***

            if ( this.picPreview.Image != null )
            {
                this.picPreview.Image.Dispose();
                this.picPreview.Image = null;
            }

            // *** Vérification sélection ***

            if ( this.lstFrames.SelectedIndices.Count > 0 )
            {
                // *** Récupération rectangle de la frame ***

                Rectangle oRect = (Rectangle)this.lstFrames.Items[this.lstFrames.SelectedIndices[0]].Tag;

                // *** Génération de l'image ***

                Bitmap oImg = new Bitmap(oRect.Width, oRect.Height);

                // *** On récupère la zone de dessin ***

                using ( Graphics oGraphics = Graphics.FromImage(oImg) )
                {
                    // *** Ecriture de l'image ***

                    oGraphics.DrawImage(this.m_oSprite.Texture, new Rectangle(0, 0, oRect.Width, oRect.Height), oRect, GraphicsUnit.Pixel);

                    // *** Cadre ***

                    if ( this.chkCadre.Checked )
                    {
                        using ( Pen oPen = new Pen(Color.Black) )
                        {
                            oGraphics.DrawRectangle(oPen, new Rectangle(0, 0, oRect.Width - 1, oRect.Height - 1));
                        }
                    }
                }

                // *** Ajout au contrôle ***

                this.picPreview.Image = oImg;
            }

            // *** Ré affichage ***

            this.picPreview.Invalidate();
        }

        /// <summary>
        /// Renumérote les frames
        /// </summary>
        private void Renumeroter()
        {
            int iCount = 1;

            foreach ( ListViewItem oItem in this.lstFrames.Items )
            {
                oItem.Text = iCount.ToString();
                iCount++;
            }
        }

        /// <summary>
        /// Sauvegarde l'animation en cours
        /// </summary>
        private void SaveAnimation()
        {
            SpriteSequence oSeq = null;

            // *** Récupération de l'item ***

            foreach ( SpriteSequence oItem in this.m_oSprite.Sequences )
            {
                if ( oItem.Name.ToLower().Equals(this.m_sCurrentEdit.ToLower()) )
                {
                    oSeq = oItem;
                    break;
                }
            }

            if ( oSeq == null )
                return;

            // *** Modification des frames ***

            oSeq.FramesIndexes.Clear();
            oSeq.Type = this.rbListe.Checked ? SpriteAnimationType.List : SpriteAnimationType.Range;

            foreach ( int iIndex in this.picAnimation.Animation )
                oSeq.FramesIndexes.Add(iIndex);
        }

        /// <summary>
        /// Modifie le statut de modification
        /// </summary>
        /// <param name="p_bMode">TRUE modifié, FALSE sinon</param>
        private void SetModify(bool p_bMode)
        {
            this.m_bSave = p_bMode;
            this.BuildTitle();

            if ( !this.m_bSave )
                this.picAnimation.ToSave = false;
        }

        #endregion

        #region Indexer

        /// <summary>
        /// Retourne les animations sprite
        /// </summary>
        public List<SpriteSequence> Animations
        {
            get
            {
                return (this.m_oSprite.Sequences);
            }
        }

        #endregion
    }
}
